#include "FileUtil.h"

#if defined(WIN32) && !defined(__CYGWIN__)
#include <io.h>
#define WINBASE_DECLARE_GET_MODULE_HANDLE_EX
#include <windows.h>
#include <winbase.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <direct.h> // for _mkdir

#define mkdir(x,y) _mkdir((x))

#if !defined(__MINGW32__)
#define stat64 _stati64
#endif

// set up for windows so acts just like unix access().
#ifndef F_OK
#define F_OK 4
#endif

#else // unix

#if defined( __APPLE__ )
// I'm not sure how we would handle this in raw Darwin
// without the AvailablilityMacros.
#include <AvailabilityMacros.h>

//>OSG_IOS
//IOS includes
#include "TargetConditionals.h"
#include <sys/cdefs.h>

#if (TARGET_OS_IPHONE)
#include <Availability.h>
// workaround a bug which appears when compiling for SDK < 4.0 and for the simulator
#if defined(__IPHONE_4_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0)
#define stat64 stat
#else
#if !TARGET_IPHONE_SIMULATOR
#define stat64 stat
#endif
#endif
#endif
//<OSG_IPHONE

// 10.5 defines stat64 so we can't use this #define
// By default, MAC_OS_X_VERSION_MAX_ALLOWED is set to the latest
// system the headers know about. So I will use this as the control
// variable. (MIN_ALLOWED is set low by default so it is
// unhelpful in this case.)
// Unfortunately, we can't use the label MAC_OS_X_VERSION_10_4
// for older OS's like Jaguar, Panther since they are not defined,
// so I am going to hardcode the number.
#if (MAC_OS_X_VERSION_MAX_ALLOWED <= 1040)
#define stat64 stat
#endif
#elif defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__DragonFly__) || \
      (defined(__hpux) && !defined(_LARGEFILE64_SOURCE))
#define stat64 stat
#endif

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#if defined(_DARWIN_FEATURE_64_BIT_INODE)
#define stat64 stat
#endif
#endif

#if defined(__ANDROID__)
#define stat64 stat
#endif

// set up _S_ISDIR()
#if !defined(S_ISDIR)
#  if defined( _S_IFDIR) && !defined( __S_IFDIR)
#    define __S_IFDIR _S_IFDIR
#  endif
#  define S_ISDIR(mode)    (mode&__S_IFDIR)
#endif

#define OSGDB_STRING_TO_FILENAME(s) s
#define OSGDB_FILENAME_TO_STRING(s) s
#define OSGDB_FILENAME_TEXT(x) x
#define OSGDB_WINDOWS_FUNCT(x) x ## A
#define OSGDB_WINDOWS_FUNCT_STRING(x) #x "A"
#if defined(WIN32) && !defined(__CYGWIN__)
#include <io.h>
#include <direct.h>
OCPP::StringVector getDirectoryContents(const std::string& dirName)
{
	OCPP::StringVector contents;

	OSGDB_WINDOWS_FUNCT(WIN32_FIND_DATA) data;
	HANDLE handle = OSGDB_WINDOWS_FUNCT(FindFirstFile)((OSGDB_STRING_TO_FILENAME(dirName) + OSGDB_FILENAME_TEXT("\\*")).c_str(), &data);
	if (handle != INVALID_HANDLE_VALUE)
	{
		do
		{
			contents.push_back(OSGDB_FILENAME_TO_STRING(data.cFileName));
		} while (OSGDB_WINDOWS_FUNCT(FindNextFile)(handle, &data) != 0);

		FindClose(handle);
	}
	return contents;
}
#else
#include <dirent.h>
OCPP::StringVector getDirectoryContents(const std::string& dirName)
{
	OCPP::StringVector contents;

	DIR* handle = opendir(dirName.c_str());
	if (handle)
	{
		dirent* rc;
		while ((rc = readdir(handle)) != NULL)
		{
			contents.push_back(rc->d_name);
		}
		closedir(handle);
	}

	return contents;
}

#endif // unix getDirectoryContexts

namespace OCPP
{
	static const char* const PATH_SEPARATORS = "/\\";
	static unsigned int PATH_SEPARATORS_LEN = 2;

	StringVector FileUtil::findFiles(const String strPath)
	{
		return getDirectoryContents(strPath);
	}

	void FileUtil::formatPathWithSeperator(String& path)
	{
		path = StringUtil::replaceAll(path, "\\", "/");
		if (!StringUtil::endsWith(path, "/", false))
		{
			path += "/";
		}
	}

	void FileUtil::formatPathNoSeperator(String& path)
	{
		path = StringUtil::replaceAll(path, "\\", "/");
		if (StringUtil::endsWith(path, "/", false))
		{
			path = path.substr(0, path.length() - 1);
		}
	}

	bool FileUtil::existFile(String fileName)
	{
		return access(fileName.c_str(), F_OK) == 0;
	}

	String FileUtil::getFilePath(String fileName)
	{
		String::size_type slash = fileName.find_last_of(PATH_SEPARATORS);
		if (slash == String::npos) return String();
		else return String(fileName, 0, slash);
	}

	bool FileUtil::deleteFile(String fileName)
	{
#if _WIN32
		return DeleteFile(fileName.c_str()) == TRUE;
#else
		return remove(fileName.c_str()) > 0;
#endif
	}

	bool FileUtil::deletFolder(String filename)
	{
#if _WIN32
		return RemoveDirectory(filename.c_str()) == TRUE;
#else
		return remove(filename.c_str()) > 0;
#endif
	}

	bool FileUtil::makeDirectory(String path)
	{
		if (path.empty())
		{
			return false;
		}

		struct stat64 stbuf;
		if (stat64(path.c_str(), &stbuf) == 0)
		{
			if (S_ISDIR(stbuf.st_mode))
				return true;
			else
			{
				return false;
			}
		}

		std::stack<String> paths;
		for (String dir = path;
			!dir.empty();
			dir = getFilePath(dir))
		{
			if (stat64(dir.c_str(), &stbuf) < 0)
			{
				switch (errno)
				{
				case ENOENT:
				case ENOTDIR:
					paths.push(dir);
					break;

				default:
					return false;
				}
			}
		}

		while (!paths.empty())
		{
			String dir = paths.top();

#if defined(WIN32)
			//catch drive name
			if (dir.size() == 2 && dir.c_str()[1] == ':') {
				paths.pop();
				continue;
			}
#endif

			if (mkdir(dir.c_str(), 0755) < 0)
			{
				// Only return an error if the directory actually doesn't exist.  It's possible that the directory was created
				// by another thread or process
				if (!existFile(dir))
				{
					return false;
				}
			}
			paths.pop();
		}
		return true;
	}

	String FileUtil::getFileExtension(String fileName)
	{
		String::size_type dot = fileName.find_last_of('.');
		String::size_type slash = fileName.find_last_of(PATH_SEPARATORS);
		if (dot == String::npos || (slash != String::npos && dot < slash)) return String("");
		return String(fileName.begin() + dot + 1, fileName.end());
	}

	bool FileUtil::renameFile(String fileName, String newfileName)
	{
		int ret = rename(fileName.c_str(), newfileName.c_str());
		return ret == 0;
	}

	FileUtil::Type FileUtil::typeOfFile(String fileName)
	{
		struct stat64 fileStat;
		if (stat64(fileName.c_str(), &fileStat) != 0)
		{
			return FileUtil::FILE_NOT_FOUND;
		} // end if

		if (fileStat.st_mode & S_IFDIR)
			return FileUtil::DIRECTORY;
		else if (fileStat.st_mode & S_IFREG)
			return FileUtil::REGULAR_FILE;

		return FileUtil::FILE_NOT_FOUND;
	}

	size_t FileUtil::sizeOfFile(String fileName)
	{
		size_t size = 0;
		std::ifstream i(fileName.c_str());
		if (i)
		{
			i.seekg(0, std::ios::end);
			size = i.tellg();
			i.close();
		}
		return size;
	}

	String FileUtil::getFileText(String fileName)
	{
		String file_text;
		std::ifstream infile(fileName, std::ios::binary);
		if (!infile) return file_text;
		std::streambuf* pbuf = infile.rdbuf();
		size_t filesize =
			static_cast<size_t>(pbuf->pubseekoff(0, std::ifstream::end));
		pbuf->pubseekoff(0, std::ifstream::beg);  // rewind
		file_text.resize(filesize);
		pbuf->sgetn(file_text.data(), filesize);
		return file_text;
	}

	bool FileUtil::writeFile(String fileName, String content)
	{
		std::ofstream ofs(fileName, std::ios::binary);
		if (ofs)
		{
			ofs.write(content.data(), content.length());
			return true;
		}
		return false;
	}

	String FileUtil::getFileBinary(String fileName)
	{
		String file_blob;
		std::ifstream infile(fileName, std::ios::binary);
		if (!infile) return file_blob;
		std::streambuf* pbuf = infile.rdbuf();
		size_t filesize =
			static_cast<size_t>(pbuf->pubseekoff(0, std::ifstream::end));
		pbuf->pubseekoff(0, std::ifstream::beg);  // rewind
		file_blob.resize(filesize);
		pbuf->sgetn(file_blob.data(), filesize);
		return file_blob;
	}

	std::vector<uint8> FileUtil::getFileBlob(String fileName)
	{
		std::vector<uint8> file_blob;
		std::ifstream infile(fileName, std::ios::binary);
		if (!infile) return file_blob;
		std::streambuf* pbuf = infile.rdbuf();
		size_t filesize =
			static_cast<size_t>(pbuf->pubseekoff(0, std::ifstream::end));
		pbuf->pubseekoff(0, std::ifstream::beg);  // rewind
		file_blob.resize(filesize);
		pbuf->sgetn((char*)file_blob.data(), filesize);
		return file_blob;
	}
}
